package moe.bogos.douro.api.common;

import net.minecraftforge.api.distmarker.Dist;

import java.util.Random;

public class Timer
{
    static Timer serverInstance = new Timer();
    static Timer clientInstance = new Timer();
    
    public static void schedule(long delay, Dist side, Runnable runnable)
    {
        if (side == Dist.CLIENT)
            clientInstance.treap.insert(delay, runnable);
        else
            serverInstance.treap.insert(delay, runnable);
    }
    
    private Treap treap;
    private long lastMi;
    
    Timer()
    {
        treap = new Treap();
        lastMi = System.currentTimeMillis();
    }
    
    void onTick()
    {
        long mi = System.currentTimeMillis();
        treap.change(mi - lastMi);
        if (treap.root == null) return;
        Treap.NodePair pair = treap.split(treap.root, 0);
        treap.root = pair.y;
        if (pair.x != null) ldr(pair.x);
        lastMi = mi;
    }
    
    void ldr(Treap.Node node)
    {
        if (node.ch[0] != null) ldr(node.ch[0]);
        node.runnable.run();
        if (node.ch[1] != null) ldr(node.ch[1]);
    }
    
    private static class Treap
    {
        private Random random;
        private Node root;
        
        private Treap()
        {
            random = new Random();
        }
        
        private NodePair split(Node now, long v)
        {
            if (now == null) return new NodePair(null, null);
            NodePair p;
            now.pushDown();
            if (now.v <= v)
            {
                p = split(now.ch[1], v);
                now.ch[1] = p.x;
                p.x = now;
            }
            else
            {
                p = split(now.ch[0], v);
                now.ch[0] = p.y;
                p.y = now;
            }
            now.pushUp();
            return p;
        }
        
        private Node merge(Node x, Node y)
        {
            if (x == null) return y;
            if (y == null) return x;
            x.pushDown();
            y.pushDown();
            if (x.rdm >= y.rdm)
            {
                x.ch[1] = merge(x.ch[1], y);
                x.pushUp();
                return x;
            }
            else
            {
                y.ch[0] = merge(x, y.ch[0]);
                y.pushUp();
                return y;
            }
        }
        
        private void insert(long v, Runnable runnable)
        {
            if (root == null)
            {
                root = new Node(v, runnable);
                return;
            }
            Node x, y;
            NodePair p = split(root, v);
            root = merge(merge(p.x, new Node(v, runnable)), p.y);
        }
        
        private void change(long x)
        {
            if (root != null) root.lazyTag += x;
        }
        
        private class Node
        {
            private Node ch[];
            private long v;
            private int rdm;
            private int siz;
            private int lazyTag;
            private Runnable runnable;
            
            private Node(long v, Runnable runnable)
            {
                ch = new Node[2];
                rdm = random.nextInt();
                this.v = v;
                siz = 1;
                lazyTag = 0;
                this.runnable = runnable;
            }
            
            private void pushUp()
            {
                siz = 1;
                if (ch[0] != null) siz += ch[0].siz;
                if (ch[1] != null) siz += ch[1].siz;
            }
            
            private void pushDown()
            {
                if (ch[0] != null) ch[0].lazyTag += lazyTag;
                if (ch[1] != null) ch[1].lazyTag += lazyTag;
                v -= lazyTag;
                lazyTag = 0;
            }
        }
        
        private class NodePair
        {
            private Node x, y;
            
            private NodePair(Node x, Node y)
            {
                this.x = x;
                this.y = y;
            }
        }
    }
}
